package com.basker.pisces.domain.command.meta;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import com.basker.pisces.core.Element;
import com.basker.pisces.core.exception.StaleObjectException;
import com.basker.pisces.core.meta.annotation.CollectionField;
import com.basker.pisces.core.meta.annotation.DataField;
import com.basker.pisces.domain.command.exception.CommandExecuteInvalidObjectException;
import com.basker.pisces.domain.command.processor.ICommandExecuteProcessor;
import com.basker.pisces.domain.command.run.CommandRunner;
import com.basker.pisces.domain.entity.meta.Entity;
import com.basker.pisces.domain.entity.meta.EntityHelper;
import com.basker.pisces.domain.property.meta.Property;
import com.basker.pisces.domain.validation.ValidationErrors;
import com.basker.pisces.domain.validation.meta.Validation;
import com.basker.pisces.domain.validation.validator.ICommandValidator;
import com.basker.pisces.domain.validation.validator.ValidatorFactory;
import com.basker.pisces.resource.StringResources;

/**
 * 命令元素基类，在命令执行时，每个命令都会创建一个与之对应的命令运行器：{@link CommandRunner}
 *
 * @author hangwen
 */
public abstract class Command extends Element {

    private static final String RUNNER_SUFFIX = "Runner";

    @DataField
    private Boolean requireReload = false;

    @DataField
    private Boolean requireFullEntity = false;

    @DataField
    private Boolean enableLock = false;

    @DataField
    private Boolean enableRetryWhileObjectStale = false;

    @DataField
    private Integer lockWaitTimeMills = 500;

    @CollectionField
    private List<Validation> validations = new ArrayList<>();

    @CollectionField
    private List<CommandExecuteProcessorPlugin> processorPlugins = new ArrayList<>();

    @DataField
    private List<String> requiredPropertyAliasList = new ArrayList<>();

    private transient Entity hostEntity;

    @DataField
    private Boolean enableDefaultDispalyNameRule = false;

    /**
     * 添加当前命令需要的属性别名
     *
     * @param requiredPropertyAliasArray
     */
    public void addRequiredPropertyAlias(String... requiredPropertyAliasArray) {
        if (ObjectUtils.isEmpty(requiredPropertyAliasArray)) {
            return;
        }

        this.requiredPropertyAliasList.addAll(Arrays.asList(requiredPropertyAliasArray));
    }

    public List<ICommandExecuteProcessor> createProcessors() {
        if (CollectionUtils.isEmpty(this.getProcessorPlugins())) {
            return Collections.emptyList();
        }

        return this.getProcessorPlugins().stream().map(p -> p.createPluginInstance()).collect(Collectors.toList());
    }

    /**
     * 创建需要使用验证器
     *
     * @return
     * @see {@link CommandRunner#initial()}
     */
    public List<ICommandValidator> createValidators() {
        if (CollectionUtils.isEmpty(this.getValidations())) {
            return Collections.emptyList();
        }

        return this.getValidations().stream().map(v -> ValidatorFactory.createEntityCommandValidator(v))
                .collect(Collectors.toList());
    }

    @Override
    public String getDisplayName() {
        if (this.getEnableDefaultDispalyNameRule() && this.hostEntity != null) {
            String key = EntityHelper.getDisplayNameResouceKey(this.hostEntity, this);
            return StringResources.resolve(key);
        }

        return super.getDisplayName();
    }

    public Boolean getEnableDefaultDispalyNameRule() {
        return enableDefaultDispalyNameRule == null ? false : enableDefaultDispalyNameRule;
    }

    /**
     * 是否需要对命令目标对象加锁，加锁后，该实体其他命令执行该对象时，会因为锁而等待，等待时间取决于{@link #getLockWaitTimeMills()}，默认false。
     * <p>
     * 注意：
     * <li>1.锁仅对传入主键的命令执行方法有效，因为先加锁再读取对象时，锁才有意义，否则即使加了锁，也无法保证先前读取的对象是最新的；
     * <li>2.如果在事务范围内执行命令，加载对象会因为select for
     * update而被数据库加锁，当前命令即使没有设置锁，也可能会因为数据库锁而等待，通常来说，业务锁的时间可自由设置，并且远远短于数据库锁，因此建议通过业务锁控制并发的有序性，而select
     * for update 确保数据的即时性。
     *
     * @return
     */
    public Boolean getEnableLock() {
        return enableLock == null ? false : enableLock;
    }

    /**
     * 当执行保存时，如果发生{@link StaleObjectException}(即违反乐观锁)时，是否启用重试机制。
     *
     * <p>
     * 注意：仅对传入主键的命令执行方法有效，反之，如果传入的是对象，在重试的时候需要重载对象，而这个步骤是命令体系之外的部分，这种情况下，只能在外部自行实现重试，后续会考虑专门为此设计一种方案。
     *
     * @return
     */
    public Boolean getEnableRetryWhileObjectStale() {
        return enableRetryWhileObjectStale;
    }

    public Entity getHostEntity() {
        return hostEntity;
    }

    /**
     * 锁的等待的毫秒数，超过该时间则获取锁失败,默认500ms
     *
     * @return
     */
    public Integer getLockWaitTimeMills() {
        return lockWaitTimeMills;
    }

    public List<CommandExecuteProcessorPlugin> getProcessorPlugins() {
        return processorPlugins;
    }

    /**
     * 获取当前命令需要的属性列表，内容是属性的别名。
     *
     * @return
     * @see {@link Property#getAlias()}
     */
    public List<String> getRequiredPropertyAliasList() {
        return requiredPropertyAliasList;
    }

    /**
     * 当前命令是否需要依赖完整的实体，如果为false，则执行器会进而根据@
     * {@link #getRequiredPropertyAliasList()}属性组装子实体，这样可以避免加载和访问到不必要的数据。默认false
     *
     * @return
     * @see {@link #getRequiredPropertyAliasList()}
     * @see {@link Entity#getSubEntity(List)}
     */
    public Boolean getRequireFullEntity() {
        return requireFullEntity == null ? false : requireFullEntity;
    }

    /**
     * 当执行命令时传入的是业务对象而非主键的时候，是否需要执行器强制从数据仓库再次加载对象，对于这类命令，建议通过传入id的方式调用,默认false
     *
     * @return
     */
    public Boolean getRequireReload() {
        return requireReload == null ? false : requireReload;
    }

    /**
     * 获取当前命令对应的运行器的实例名称,它是一个spring
     * bean，默认的名称为Command的简单类名+Runner，首字母小写，如SaveCommand对应saveCommandRunner
     *
     * @return
     */
    public String getRunnerBeanName() {
        return StringUtils.uncapitalize(this.getClass().getSimpleName()) + RUNNER_SUFFIX;
    }

    /**
     * 获取执行命令过程中，需要的校验规则
     *
     * @return
     */
    public List<Validation> getValidations() {
        return validations;
    }

    public void setEnableDefaultDispalyNameRule(Boolean enableDefaultDispalyNameRule) {
        this.enableDefaultDispalyNameRule = enableDefaultDispalyNameRule;
    }

    public void setEnableLock(Boolean enableLock) {
        this.enableLock = enableLock;
    }

    public void setEnableRetryWhileObjectStale(Boolean enableRetryWhileObjectStale) {
        this.enableRetryWhileObjectStale = enableRetryWhileObjectStale;
    }

    public void setHostEntity(Entity hostEntity) {
        this.hostEntity = hostEntity;
    }

    public void setLockWaitTimeMills(Integer lockWaitTimeMills) {
        this.lockWaitTimeMills = lockWaitTimeMills;
    }

    public void setProcessorPlugins(List<CommandExecuteProcessorPlugin> processorPlugins) {
        this.processorPlugins = processorPlugins;
    }

    public void setRequiredPropertyNameList(List<String> requiredPropertyNameList) {
        this.requiredPropertyAliasList = requiredPropertyNameList;
    }

    public void setRequireFullEntity(Boolean requireFullEntity) {
        this.requireFullEntity = requireFullEntity;
    }

    public void setRequireReload(Boolean requireReload) {
        this.requireReload = requireReload;
    }

    public void setValidations(List<Validation> validations) {
        this.validations = validations;
    }

    /**
     * 命令可以将验证错误包装成一个特定的异常
     *
     * @param errors
     * @return 返回CommandExecuteInvalidObjectException或者null, null的话，命令执行器也会创建一个默认的异常对象
     */
    public CommandExecuteInvalidObjectException wrapperValidationErrors(ValidationErrors errors) {
        return null;
    }

}
